package data;

import java.io.PrintStream;
import java.util.ArrayList;
import static conf.Configuration.*;

/**
 * A student who will be taking a Knight School class.
 * 
 * @author Frans Honig, Maxime Serrano, Raphael Townshend
 */
public class Student implements DateComparable<Student>
{	
	/**
	 * The number of classes that are left to pick from.
	 */
	private int numClassesLeftToPickFrom = NUM_CLASS_CHOICES;
	
	/**
	 * The data for the student.
	 */
	public StudentData data;
	
	public int position;
	
	/**
	 * Whether or not the student is enrolled in a class.
	 */
	private boolean placed = false;
	
	/**
	 * A temporary class choices array, used to hold the choices while the
	 * algorithm is running.
	 */
	private int[] tempClassChoices;
	
	/**
	 * Used to generate choice numbers.
	 */
	private int[] classChoices;
	
	/**
	 * How many times this student has been placed in a course.
	 */
	private int numPlacements = 0;
	
	/**
	 * The ID of the course that this student is currently enrolled in.
	 */
	public int courseEnrolledIn = 0;
	
	/**
	 * Whether or not the student is forced into his second choice.
	 */
	private boolean forceSecondChoice = false;
	
	/**
	 * Creates a new student with the given data.
	 * @param data The student's data.
	 */
	public Student(StudentData data)
	{
		this.data = data;
		tempClassChoices = new int[data.classChoices.length];
		classChoices = tempClassChoices;
		for (int i = 0; i < data.classChoices.length; i++)
		{
			tempClassChoices[i] = data.classChoices[i];
		}
	}
	
	/**
	 * Accessor.
	 * @return Whether or not the student is a student teacher.
	 */
	public boolean isTeacher()
	{
		return data.studentTeacher;
	}
	
	/**
	 * Accessor.
	 * @return Whether or not the student is on a trip.
	 */
	public boolean isOnTrip()
	{
		return data.onTrip;
	}
	
	/**
	 * Accessor.
	 * @return Returns the "class weight" for the student.
	 */
	public int getClassWeight()
	{
		return data.grade.weight;
	}
	
	/**
	 * Accessor.
	 * @return Whether or not the student is male.
	 */
	public boolean isMale()
	{
		return data.isMale;
	}
	
	/**
	 * Accessor.
	 * @return Whether or not the student is female.
	 */
	public boolean isFemale()
	{
		return !isMale();
	}
	
	/**
	 * Accessor.
	 * @return The student's grade level.
	 */
	public Grade getGrade()
	{
		return data.grade;
	}
	
	/**
	 * Accessor.
	 * @return The student's ID.
	 */
	public int getStudentID()
	{
		return data.studentID;
	}
	
	/**
	 * Accessor.
	 * @param classID The class to calculate the score for.
	 * @return The desirability of the course for the student.
	 */
	public double getDesirabilityScore(int classID)
	{
		if (isPlaced())
		{
			return -1;
		}
		for (int i = 0; i < numClassesLeftToPickFrom; i++)
		{
			if(tempClassChoices[i] == classID)
			{
				double score = (NUM_CLASS_CHOICES - i) * 
							   (data.grade.weight + DESIRABILITY_GRADE_OFFSET) *
							   (data.grade.weight + DESIRABILITY_GRADE_OFFSET) /
							   (DESIRABILITY_DIVISOR_OFFSET + getAbsoluteChoiceNumber(classID));
				
				if (data.isLate)
				{
					score /= DESIRABILITY_LATE_DENOMINATOR;
				}
				
				if (data.studentTeacher)
				{
					score *= DESIRABILITY_TEACHER_MULTIPLIER;
				}
				
				return score;
			}
		}
		return -1;
	}
	
	/**
	 * Gets the real choice number for the given class.
	 * @param classID The ID of the class.
	 * @return The absolute choice number for said class.
	 */
	private double getAbsoluteChoiceNumber(int classID)
	{
		for(int i = 0; i < NUM_CLASS_CHOICES; i++)
		{
			if(classID == classChoices[i])
			{
				return i + 1;
			}
		}
		return 1;
	}
	
	/**
	 * Accessor.
	 * @param classID The ID of the class to test against.
	 * @return Whether or not the class is the student's top pick.
	 */
	public boolean topPick(int classID)
	{
		return tempClassChoices[0] == classID;
	}
	
	/**
	 * Removes the given class from the student's list of choices.
	 * @param classID The classID to be removed.
	 */
	public void removePick(int classID)
	{
		for (int i = 0; i < numClassesLeftToPickFrom; i++)
		{
			if(tempClassChoices[i] == classID)
			{
				for (int j = i; j < numClassesLeftToPickFrom - 1; j++)
				{
					tempClassChoices[j] = tempClassChoices[j + 1]; 
				}
			}
		}
	}
	
	/**
	 * Returns a string representation of the student.
	 */
	public String toString()
	{
		String val = "[Student] ";
		val += "ID: " + data.studentID;
		val += ", name: " + data.studentName;
		val += ", user: " + data.studentUser;
		val += ", placed: " + placed;
		val += ", male: " + data.isMale;
		val += ", grade: " + data.grade;
		val += ", late: " + data.isLate;
		val += ", teacher: " + data.studentTeacher;
		val += ", onTrip: " + data.onTrip;
		val += ", position: " + position;
		val += ", class: " + courseEnrolledIn;
		for (int i = 0; i < NUM_CLASS_CHOICES; i++)
		{
			val += ", choice #" + i + " " + data.classChoices[i];
		}
		val += ".";
		return val;
	}
	
	/**
	 * Comparison method.
	 */
	public int compareTo(Student s)
	{
		return data.studentID - s.getStudentID();
	}
	
	/**
	 * Other comparison method.
	 */
	public int compareToDate(Student s)
	{
		return data.submissionDate.compareTo(s.data.submissionDate);
	}
	
	/**
	 * Calculates the heuristic score for the given class ID.
	 * @param classId The ID of the class enrolled in.
	 * @return What the heuristic for this student in this course is.
	 */
	public int calcHeuristicScore(int classId)
	{
		if (data.onTrip)
		{
			// people on trips count as having gotten their first choice
			
			return (data.grade.weight + GRADE_HEURISTIC_OFFSET);
		}
		else if (forceSecondChoice)
		{
			return SECOND_CHOICE_MULTIPLIER * (data.grade.weight + GRADE_HEURISTIC_OFFSET);
		}
		
		for (int i = 0; i < data.classChoices.length; i++)
		{
			if (data.classChoices[i] == classId)
			{
				if (data.isLate)
				{
					return ((i + CHOICE_HEURISTIC_OFFSET) * LATE_WEIGHT);
				}
				else
				{
					return ((i + CHOICE_HEURISTIC_OFFSET) * (data.grade.weight + GRADE_HEURISTIC_OFFSET));
				}
			}
		}
		return 0;
	}
	
	/**
	 * Tells whether or not the given course is choice number choice for this student.
	 * @param classID The ID of the class.
	 * @param choice The choice number to test.
	 * @return Whether or not the class is said choice number for this student.
	 */
	public boolean courseIsChoice(int classID, int choice)
	{
		if (choice < 0 || choice >= NUM_CLASS_CHOICES)
		{
			return false;
		}
		else if (data.classChoices[choice] == classID)
		{
			return true;
		}
		
		return false;
	}
	
	/**
	 * Which choice number the student is enrolled in.
	 * @return The choice number to use.
	 */
	public int getEnrolledChoiceNumber()
	{
		for (int choice = 0; choice < NUM_CLASS_CHOICES; choice++)
		{
			if(courseIsChoice(courseEnrolledIn, choice))
			{
				return choice;
			}
		}
		return 6;
	}
	
	/**
	 * Accessor.
	 * @return The student's last choice.
	 */
	public int getLastChoice()
	{
		return data.classChoices[data.classChoices.length - 1];
	}
	
	/**
	 * Places the student in the given course.
	 * @param courseID The ID of the course to be placed in.
	 */
	public void place(int courseID, int position)
	{
		this.position = position;
		placed = true;
		numPlacements++;
		if (DEBUG)
		{
			System.out.println("Placed student #" + data.studentID + " into course:" + courseID);
		}
		courseEnrolledIn = courseID;
	}
	
	/**
	 * Accessor.
	 * @return Whether or not the student is in a class.
	 */
	public boolean isPlaced()
	{
		return placed;
	}
	
	/**
	 * Forces the student to count as having his or her second choice.
	 */
	public void forceSecondChoice()
	{
		forceSecondChoice = true;
	}
	
	/**
	 * Accessor.
	 * @return Whether or not the student is forced to have his or her second choice.
	 */
	public boolean hasSecondChoiceForced()
	{
		return forceSecondChoice;
	}
	
	/**
	 * Whether or not the student is eligible for the course given.
	 * @param courseID The ID of the course to check against.
	 * @return Whether or not the student is allowed to join said course.
	 */
	public boolean eligible(int courseID)
	{
		for (int i = 0; i < COURSE_CHOICE_BAR; i++)
		{
			if (tempCourseIsChoice(courseID, i))
			{
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Used only here. Same as courseIsChoice, but checks against tempClassChoices instead.
	 * @param classID The class ID to check
	 * @param choice The index to check.
	 * @return Whether or not said course is said choice.
	 */
	private boolean tempCourseIsChoice(int classID, int choice)
	{
		if (choice < 0 || choice >= NUM_CLASS_CHOICES)
		{
			return false;
		}
		else if (tempClassChoices[choice] == classID)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	
	/**
	 * Resets the student, since it was in a course that has been deleted.
	 */
	public void reset()
	{
		tempClassChoices = new int[data.classChoices.length];
		classChoices = tempClassChoices;
		for (int i = 0; i < data.classChoices.length; i++)
		{
			tempClassChoices[i] = data.classChoices[i];
		}
		
		forceSecondChoice = false;
		numPlacements = 0;
		courseEnrolledIn = 0;
		placed = false;
	}
	
	public boolean isForcedLate()
	{
		return (data.isLate && data.classChoices[1] == -1);
	}
	
	public boolean hasCourseAsChoice(Course c)
	{
		for (int i = 0; i < NUM_CLASS_CHOICES; i++)
		{
			if (courseIsChoice(c.getID(), i))
			{
				return true;
			}
		}
		return false;
	}
	
	public boolean equals(Student s)
	{
		return s.data.studentName.equals(data.studentName);
	}
	
	public void noLongerHasSecondChoiceForced()
	{
		forceSecondChoice = false;
	}
	
	public int[] getChoices()
	{
		int[] returnVal = new int[data.classChoices.length];
		
		for (int i = 0; i < data.classChoices.length; i++)
		{
			returnVal[i] = data.classChoices[i];
		}
		
		return returnVal;
	}
	
	public boolean isRandomlyPlaced()
	{
		for (int i = 0; i < data.classChoices.length; i++)
		{
			if (data.classChoices[i] == courseEnrolledIn)
			{
				return false;
			}
		}
		return true;
	}
	
	public void mrThibodeauxFormat(PrintStream out, ArrayList<Course> courses)
	{
		if (!(isOnTrip()))
		{
			for (Course c : courses)
			{
				if(c.getID() == courseEnrolledIn)
				{
					out.println('"' + data.studentName + "\",\"" + c.getName() + '"');
				}
			}
		}
		else
		{
			out.println('"' + data.studentName + "\",\"trip\"");
		}
	}
	
	public void ourValidatorFormat(PrintStream out)
	{
		if (!(isOnTrip()))
		{
			int courseChoice = 7;
			for (int i = 0; i < data.classChoices.length; i++)
			{
				if (data.classChoices[i] == courseEnrolledIn)
				{
					courseChoice = i + 1;
				}
			}
			
			out.println(toString() + ": " + getStudentID() + "," + getGrade()+ "," + courseChoice + "," + data.isLate + "," + data.studentName + "," + data.studentUser);
			if (courseChoice == 7)
			{
				System.out.println(this);
				System.out.println("Placed into: " + courseEnrolledIn);
			}
		}
		else
		{
			out.println(toString() + ": " + getStudentID() + "," + getGrade()+ "," + 1 + "," + data.isLate + "," + data.studentName + "," + data.studentUser);
			}
		
	}
}
